package org.p2psimulator.entity.overlaynetwork;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.p2psimulator.config.GeneralConfig;
import org.p2psimulator.engine.Environment;
import org.p2psimulator.entity.Id;
import org.p2psimulator.entity.OverlayNetwork;
import org.p2psimulator.entity.Peer;
import org.p2psimulator.entity.behavior.HonestBehavior;
import org.p2psimulator.entity.id.DoubleId;
import org.p2psimulator.entity.overlaynetwork.routingtable.ChordFingerTable;
import org.p2psimulator.entity.overlaynetwork.routingtable.RoutingTable;
import org.p2psimulator.entity.overlaynetwork.structures.ChordRing;
import org.p2psimulator.entity.protocol.structured.ChordProtocol;
import org.p2psimulator.exceptions.OverlayNetworkException;
import org.p2psimulator.util.Tuple;

/**
 * Estrutura de rede do Chord. Mantem as fingerTables (tabelas de roteamento) de
 * todos os peers e implementa mecanismos e entrada e sa�da de peers, assim como
 * c�lculo de sucessores e predecessores
 *
 * @author amorim
 *
 */
public class ChordOverlay extends OverlayNetwork {

    GeneralConfig config;
    private int m = 8;
    private ChordRing chordRing;

    public ChordOverlay(Environment env) {
        super(env);
        this.config = env.getGeneralConfig();
        this.m = config.getFileConfig().getIdSpace();
        this.chordRing = new ChordRing(config.getFileConfig().getIdSpace());

    }

    /**
     * para testes. recebe apenas o tamanho do espaço de endereçamento.
     *
     * @param space
     */
    public ChordOverlay(Environment env, int space) {
        super(env);
        this.m = space;
        this.chordRing = new ChordRing(space);

    }

    @Override
    public RoutingTable getRoutingTable(Id idPeer) throws OverlayNetworkException {
        return chordRing.getValue((DoubleId) idPeer);
    }

    @Override
    public void generateOverlay() throws OverlayNetworkException {

            // generate overlay
            for (Id id : env.getPeerBank().getPeerMap().keySet()) {
                ChordFingerTable table = new ChordFingerTable(this.m, id);
                chordRing.insertNode(id, table);
            }
            for (Id id : env.getPeerBank().getPeerMap().keySet()) {
                chordRing.fillFingers(id, m);
                if (!onlinePeers.contains(id)) {
                    onlinePeers.add(id);
                }
            }
    }

    @Override
    public void joinNetwork(Id idPeer) throws OverlayNetworkException {

        ChordFingerTable table = chordRing.getValue(idPeer);
        chordRing.insertNode(idPeer, table);
        chordRing.fillFingers(idPeer, m);
        if (offlinePeers.contains(idPeer)) {
            offlinePeers.remove(idPeer);
        }
        if (!onlinePeers.contains(idPeer)) {
            onlinePeers.add(idPeer);
        }
    }

    @Override
    public void leaveNetwork(Id idPeer) {

        ChordFingerTable table;
        try {
            table = chordRing.getValue(idPeer);
            if (table == null) {
                return;
            } else {
                table.setSucessor(table.getOwner());
                table.setPredecessor(table.getOwner());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!offlinePeers.contains(idPeer)) {
            offlinePeers.add(idPeer);
        }
        if (onlinePeers.contains(idPeer)) {
            onlinePeers.remove(idPeer);
        }

    }

    @Override
    public String printOverlay() {

        StringBuilder str = new StringBuilder("[ ChordOverlay ]\n");
        for (Tuple<Id<Double>, ChordFingerTable> tuple : chordRing.getRing()) {
            str.append("p" + tuple.getB().getPredecessor() + " - [" + tuple.getA() + "]: s"
                    + tuple.getB().getSucessor() + " - ");
            for (Id id : tuple.getB().getFingers()) {
                str.append(id + " - ");
            }
            str.append("\n\n");
        }

        return str.toString();
    }

    public ChordFingerTable getFingerTable(Id id) throws OverlayNetworkException {
        if (this.chordRing.getValue(id) == null) {
            ChordFingerTable table = new ChordFingerTable(this.m, id);
            table.setSucessor(id);
            table.setPredecessor(id);
            this.chordRing.insertNodeClean(id, table);
        }
        return this.chordRing.getValue(id);
    }

    public ChordRing getChordRing() {
        return chordRing;
    }

    public void setChordRing(ChordRing chordRing) {
        this.chordRing = chordRing;
    }

    public int getM() {
        return m;
    }

    public void setM(int m) {
        this.m = m;
    }
}
